package btree;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import exceptions.*;

import global.PageId;
import global.RID;
import diskmgr.Page;

public class BTIndexPage extends BTSortedPage {

	public BTIndexPage(Page arg0, int arg1) throws IOException {
		super(arg0, arg1);
		setType(NodeType.INDEX);
	}

	public BTIndexPage(int arg0) throws ConstructPageException, IOException {
		super(arg0);
		setType(NodeType.INDEX);
	}

	public BTIndexPage(PageId pid, int arg0) throws ConstructPageException,
			IOException {
		super(pid, arg0);
		setType(NodeType.INDEX);
	}

	public KeyDataEntry split(KeyDataEntry dataEntry, 
			PageId currentPID) throws ConstructPageException, IOException,
			IteratorException, IndexInsertRecException, DeleteRecException,
			KeyNotMatchException, NodeNotMatchException, ConvertException,
			UnpinPageException {
		 int keyType = BTreeFile.headerPage.getKeyType();
		 BTreeHeaderPage headerPage = BTreeFile.headerPage;
		// PageId currentPID = this.getCurPage();
		KeyDataEntry tmpEntry;
		RID delRid = new RID();
		// BTIndexPage currentIndexPage = this;
		BTIndexPage newIndexPage = new BTIndexPage(keyType);
		PageId newIndexPageId = newIndexPage.getCurPage();
		DataOutputStream trace = BTreeFile.trace;
		if (trace != null) {
			if (headerPage.getRoot().pid != currentPID.pid)
				trace.writeBytes("SPLIT node " + currentPID
						+ " IN nodes " + currentPID + " "
						+ newIndexPageId + BTreeFile.lineSep);
			else
				trace.writeBytes("ROOTSPLIT IN nodes " + currentPID
						+ " " + newIndexPageId + BTreeFile.lineSep);
			trace.flush();
		}
		for (tmpEntry = this.getFirst(delRid); tmpEntry != null; tmpEntry = this
				.getFirst(delRid)) {
			newIndexPage.insertKey(tmpEntry.key,
					((IndexData) tmpEntry.data).getData());
			this.deleteSortedRecord(delRid);
		}
		RID firstRid = new RID();
		KeyDataEntry undoEntry = null;
		for (tmpEntry = newIndexPage.getFirst(firstRid); (this
				.available_space() > newIndexPage.available_space()); tmpEntry = newIndexPage
				.getFirst(firstRid)) {
			// now insert the <key,pageId> pair on the new
			// index page
			undoEntry = tmpEntry;
			this.insertKey(tmpEntry.key, ((IndexData) tmpEntry.data).getData());
			newIndexPage.deleteSortedRecord(firstRid);
		}
		if (this.available_space() < newIndexPage.available_space()) {

			newIndexPage.insertKey(undoEntry.key,
					((IndexData) undoEntry.data).getData());

			this.deleteSortedRecord(new RID(this.getCurPage(), (int) this
					.getSlotCnt() - 1));
		}
		// check whether <newKey, newIndexPageId>
		// will be inserted
		// on the newly allocated or on the old index page

		tmpEntry = newIndexPage.getFirst(firstRid);

		if (BT.keyCompare(dataEntry.key, tmpEntry.key) >= 0) {
			// the new data entry belongs on the new index page
			newIndexPage.insertKey(dataEntry.key,
					((IndexData) dataEntry.data).getData());
		} else {
			this.insertKey(dataEntry.key,
					((IndexData) dataEntry.data).getData());

			int i = (int) this.getSlotCnt() - 1;
			tmpEntry = BT.getEntryFromBytes(this.getpage(),
					this.getSlotOffset(i), this.getSlotLength(i), keyType,
					NodeType.INDEX);

			newIndexPage.insertKey(tmpEntry.key,
					((IndexData) tmpEntry.data).getData());

			this.deleteSortedRecord(new RID(this.getCurPage(), i));

		}
		BTreeFile.unpinPage(currentPID);
		dataEntry = newIndexPage.getFirst(delRid);
		newIndexPage.setPrevPage(((IndexData) dataEntry.data).getData());

		// delete first record on new index page since it is given up
		newIndexPage.deleteSortedRecord(delRid);
		BTreeFile.unpinPage(newIndexPageId);
//		if (trace != null) {
//			trace_children(currentIndexPageId);
//			trace_children(newIndexPageId);
//		}
		((IndexData) dataEntry.data).setData(newIndexPageId);
		return dataEntry;
	}

	public KeyDataEntry split(KeyClass data, PageId pid) {
		try {
			BTIndexPage next = new BTIndexPage(this.keyType);
			BTIndexPage oldNext = new BTIndexPage(this.getNextPage(),
					this.keyType);
			next.setNextPage(oldNext.getCurPage());
			next.setPrevPage(this.getCurPage());
			this.setNextPage(next.getCurPage());
			oldNext.setPrevPage(next.getCurPage());
			next.insertKey(data, pid);
			int numberOfRecords = numberOfRecords();
			KeyDataEntry currentEntry = null;
			for (int i = numberOfRecords - 1; i >= numberOfRecords / 2; i--) {
				RID startRecored = new RID(this.getCurPage(), i);
				currentEntry = getNext(startRecored);
				// this.delEntry(currentEntry); need to implement this method
				next.insertKey(currentEntry.key,
						((IndexData) (currentEntry.data)).getData());
			}
			return currentEntry;
		} catch (ConstructPageException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IteratorException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IndexInsertRecException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public RID insertKey(KeyClass key, PageId pageNo)
			throws IndexInsertRecException {

		RID rid;
		KeyDataEntry entry;
		try {
			entry = new KeyDataEntry(key, pageNo);
			rid = super.insertRecord(entry);
			return rid;
		} catch (Exception e) {
			throw new IndexInsertRecException(e, "Insert failed");

		}
	}

	public PageId getPageNoByKey(KeyClass key) throws IOException {

		for (int i = getSlotCnt() - 1; i >= 0; i--) {

			try {
				KeyDataEntry entry = BT.getEntryFromBytes(getpage(),
						getSlotOffset(i), getSlotLength(i), keyType,
						NodeType.INDEX);
				if (BT.keyCompare(entry.key, key) <= 0) {
					IndexData data = (IndexData) entry.data;
					return data.getData();
				}
			} catch (KeyNotMatchException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (NodeNotMatchException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ConvertException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;

	}

	public KeyDataEntry getFirst(RID rid) throws IteratorException {
		KeyDataEntry entry;

		try {
			rid.pageNo = getCurPage();
			rid.slotNo = 0; // begin with first slot

			if (getSlotCnt() <= 0) {
				return null;
			}

			entry = BT.getEntryFromBytes(getpage(), getSlotOffset(0),
					getSlotLength(0), keyType, NodeType.INDEX);

			return entry;
		} catch (Exception e) {
			throw new IteratorException(e, "Get first entry failed");
		}

	}

	public KeyDataEntry getNext(RID rid) throws IteratorException {
		KeyDataEntry entry;

		try {
			rid.pageNo = getCurPage();
			rid.slotNo = rid.slotNo + 1;
			if (rid.slotNo >= getSlotCnt()) {
				return null;
			}
			entry = BT.getEntryFromBytes(getpage(), getSlotOffset(rid.slotNo),
					getSlotLength(rid.slotNo), keyType, NodeType.LEAF);

			return entry;
		} catch (Exception e) {
			throw new IteratorException(e, "Get next entry failed");
		}

	}
}
